package com.kexilo.core.common.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.jsontype.impl.LaissezFaireSubTypeValidator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;

/**
 * Redis配置
 * 
 * @author Kexilo
 */
@Configuration
public class RedisConfig {
    
    private static final Logger log = LoggerFactory.getLogger(RedisConfig.class);

    /**
     * RedisTemplate配置
     */
    @Bean
    @SuppressWarnings(value = {"unchecked", "rawtypes"})
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<Object, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        // 使用Jackson2JsonRedisSerializer来序列化和反序列化redis的value值
        GenericJackson2JsonRedisSerializer jsonRedisSerializer = createJsonRedisSerializer();

        // 使用StringRedisSerializer来序列化和反序列化redis的key值
        StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();

        // key采用String的序列化方式
        template.setKeySerializer(stringRedisSerializer);
        // hash的key也采用String的序列化方式
        template.setHashKeySerializer(stringRedisSerializer);
        // value序列化方式采用jackson
        template.setValueSerializer(jsonRedisSerializer);
        // hash的value序列化方式采用jackson
        template.setHashValueSerializer(jsonRedisSerializer);
        
        template.afterPropertiesSet();
        
        log.info("RedisTemplate配置完成");
        return template;
    }

    /**
     * Redis限流脚本
     */
    @Bean
    public DefaultRedisScript<Long> limitScript() {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(buildLuaScript());
        redisScript.setResultType(Long.class);
        return redisScript;
    }

    /**
     * 创建JSON序列化器
     */
    private GenericJackson2JsonRedisSerializer createJsonRedisSerializer() {
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        
        // 设置类型信息，用于反序列化
        om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance, 
                                ObjectMapper.DefaultTyping.NON_FINAL, 
                                JsonTypeInfo.As.WRAPPER_ARRAY);
        
        return new GenericJackson2JsonRedisSerializer(om);
    }

    /**
     * 构建限流Lua脚本
     */
    private String buildLuaScript() {
        return """
            local key = KEYS[1]
            local window = tonumber(ARGV[1])
            local limit = tonumber(ARGV[2])
            local current = tonumber(ARGV[3])
            
            -- 移除过期的请求记录
            redis.call('ZREMRANGEBYSCORE', key, 0, current - window * 1000)
            
            -- 获取当前窗口内的请求数量
            local count = redis.call('ZCARD', key)
            
            if count < limit then
                -- 添加当前请求到有序集合
                redis.call('ZADD', key, current, current)
                -- 设置过期时间
                redis.call('EXPIRE', key, window + 1)
                return 1
            else
                return 0
            end
            """;
    }

    /**
     * 分布式锁Lua脚本
     */
    @Bean
    public DefaultRedisScript<String> lockScript() {
        DefaultRedisScript<String> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(buildLockScript());
        redisScript.setResultType(String.class);
        return redisScript;
    }

    /**
     * 构建分布式锁Lua脚本
     */
    private String buildLockScript() {
        return """
            if redis.call('set', KEYS[1], ARGV[1], 'NX', 'PX', ARGV[2]) then
                return 'OK'
            else
                return 'FAIL'
            end
            """;
    }

    /**
     * 释放分布式锁Lua脚本
     */
    @Bean
    public DefaultRedisScript<Long> unlockScript() {
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        redisScript.setScriptText(buildUnlockScript());
        redisScript.setResultType(Long.class);
        return redisScript;
    }

    /**
     * 构建释放锁Lua脚本
     */
    private String buildUnlockScript() {
        return """
            if redis.call('get', KEYS[1]) == ARGV[1] then
                return redis.call('del', KEYS[1])
            else
                return 0
            end
            """;
    }
}
